package polygon.util;

import org.apache.log4j.Logger;
import polygon.exception.ProcessRunException;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author Mike Mirzayanov
 */
public class ProcessUtil {
    private static final Logger logger = Logger.getLogger(ProcessUtil.class);

    /**
     * @param commandLineItem Command line item.
     * @return Returns string array. Each element is a single item in terms of command line argument.
     *         Works with quotes correct.
     */
    public static String[] splitCommandLineItem(String commandLineItem) {
        // Seems to be file name path?
        if (new File(commandLineItem).exists()) {
            return new String[]{commandLineItem};
        }

        // Hack to generalize situation.
        commandLineItem += " ";

        // Number of slashes in the last block modulo 2.
        int slashes = 0;

        // If we are inside quotes.
        boolean quoted = false;

        // Current item.
        StringBuffer current = new StringBuffer();

        // Result.
        List<String> foundItems = new ArrayList<String>();

        for (int i = 0; i < commandLineItem.length(); i++) {
            char c = commandLineItem.charAt(i);

            if (c == '\\') {
                slashes ^= 1;
                if (slashes == 0) {
                    current.append('\\');
                }
            } else {
                if (c == '\"') {
                    if (slashes == 0) {
                        quoted = !quoted;
                    } else {
                        current.append('\"');
                    }
                } else {
                    if (slashes == 1) {
                        current.append('\\');
                    }
                    if (c <= ' ' && !quoted) {
                        if (current.length() > 0) {
                            foundItems.add(current.toString());
                            current.setLength(0);
                        }
                    } else {
                        current.append(c);
                    }
                }
                slashes = 0;
            }
        }

        return foundItems.toArray(new String[foundItems.size()]);
    }

    /**
     * Iterates through each item in commandLine and if it looks like several items split them.
     * If some item quoted it will not be splitted.
     *
     * @param commandLine Command line items.
     * @return Splitted command line.
     */
    public static String[] splitCommandLine(String... commandLine) {
        List<String> splitted = new ArrayList<String>();
        for (String item : commandLine) {
            String[] items = splitCommandLineItem(item);
            splitted.addAll(Arrays.asList(items));
        }
        return splitted.toArray(new String[splitted.size()]);
    }

    public static String runAndReturnOutput(File directory, String... args) throws ProcessRunException {
        return runAndReturnOutput(false, directory, args);
    }

    public static String runAndReturnOutput(final boolean printOutput, File directory, String... args) throws ProcessRunException {
        args = splitCommandLine(args);

        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.directory(directory);
        processBuilder.command(args);

        StringBuffer commandLine = new StringBuffer();
        for (String arg : args) {
            commandLine.append(' ').append(arg);
        }

        logger.debug("Running" + commandLine + " [" + directory + "]");

        final StringBuffer output = new StringBuffer();
        final StringBuffer error = new StringBuffer();
        final Process process;

        try {
            process = processBuilder.start();
            Thread readOutputThread = new Thread() {
                public void run() {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                    try {
                        while (true) {
                            String line = reader.readLine();
                            if (line == null) {
                                break;
                            }
                            if (printOutput) {
                                System.out.println(line);
                            }
                            output.append(line).append("\r\n");
                        }
                    } catch (IOException e) {
                        // No operations.
                    }
                }
            };
            Thread readErrorThread = new Thread() {
                public void run() {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                    try {
                        while (true) {
                            String line = reader.readLine();
                            if (line == null) {
                                break;
                            }
                            output.append(line).append("\r\n");
                        }
                    } catch (IOException e) {
                        // No operations.
                    }
                }
            };

            readOutputThread.start();
            readErrorThread.start();
            process.waitFor();
            readOutputThread.join();
            readErrorThread.join();

            StringBuffer log = new StringBuffer();
            log.append("Exit code: ").append(process.exitValue()).append(" [output = \"").append(output).append("\", error = \"").append(error).append("\"].");
            logger.debug(log);

            if (process.exitValue() != 0) {
                String formattedOutput;

                if (output.length() > 128) {
                    formattedOutput = output.substring(0, 128);
                } else {
                    formattedOutput = output.toString();
                }

                throw new ProcessRunException("Process " + args[0]
                        + " returned code " + process.exitValue() + " [output = '" + formattedOutput + "'].");
            }
            return output.toString();
        } catch (IOException e) {
            throw new ProcessRunException(e);
        } catch (InterruptedException e) {
            throw new ProcessRunException(e);
        }
    }

    public static void run(File directory, String... args) throws ProcessRunException {
        runAndReturnOutput(directory, args);
    }
}
